<!-- nbmade-vue3/src/views/iot/rule-editor/index.vue -->
<template>
    <div class="rule-editor-container">
    <!-- 工具栏 -->
    <div class="editor-toolbar">
      <div class="toolbar-left">
        <el-button type="primary" @click="saveFlow">
          <i class="el-icon-document"></i> 保存
        </el-button>
        <el-button @click="openFlowDialog">
          <i class="el-icon-folder-opened"></i> 打开
        </el-button>
        <el-button type="success" @click="deployFlow" :disabled="!hasNodes">
          <i class="el-icon-video-play"></i> 部署
        </el-button>
        <el-button type="warning" @click="stopFlow">
          <i class="el-icon-video-pause"></i> 停止
        </el-button>
        <el-button @click="clearFlow">
          <i class="el-icon-delete"></i> 清空
        </el-button>
        <el-divider direction="vertical"></el-divider>
        <el-button @click="openImport">
          <i class="el-icon-upload2"></i> 导入
        </el-button>
        <el-button @click="openPreview">
          <i class="el-icon-view"></i> 预览JSON
        </el-button>
        <el-button @click="exportFlow">
          <i class="el-icon-download"></i> 导出
        </el-button>
        <el-button @click="closeEditor" type="info" plain>
          <i class="el-icon-close"></i> 关闭
        </el-button>
        </div>
      
      <div class="toolbar-right">
        <el-input 
          v-model="flowName" 
          placeholder="Flow名称" 
          style="width: 200px; margin-right: 10px;"
        />
        <el-input 
          v-model="flowDescription" 
          placeholder="Flow描述" 
          style="width: 300px;"
        />
        </div>
      </div>
      
      <div class="editor-content">
        <!-- 节点面板 -->
        <div class="node-palette">
        <div class="palette-header">
          <h3>节点库</h3>
        </div>
        
        <div class="palette-sections">
          <!-- 输入节点 -->
          <div class="palette-section">
            <div class="section-title">
              <i class="el-icon-connection"></i>
              <span>输入</span>
            </div>
            <div class="node-items">
              <div 
                class="node-item" 
                draggable="true"
                @dragstart="onNodeDragStart"
                data-type="device-event"
              >
                <div class="node-icon device-event"></div>
                <div class="node-info">
                  <div class="node-name">设备事件</div>
                  <div class="node-desc">监听设备上下线</div>
            </div>
              </div>
              
              <div 
                class="node-item" 
                draggable="true"
                @dragstart="onNodeDragStart"
                data-type="mqtt-in"
              >
                <div class="node-icon mqtt-in"></div>
                <div class="node-info">
                  <div class="node-name">MQTT输入</div>
                  <div class="node-desc">接收MQTT消息</div>
            </div>
          </div>
          
              <div 
                class="node-item" 
                draggable="true"
                @dragstart="onNodeDragStart"
                data-type="http-in"
              >
                <div class="node-icon http-in"></div>
                <div class="node-info">
                  <div class="node-name">HTTP输入</div>
                  <div class="node-desc">接收HTTP请求</div>
                </div>
              </div>

              <!-- 添加定时器节点 -->
              <div 
                class="node-item" 
                draggable="true"
                @dragstart="onNodeDragStart"
                data-type="timer"
              >
                <div class="node-icon timer"></div>
                <div class="node-info">
                  <div class="node-name">定时器</div>
                  <div class="node-desc">定时触发规则</div>
                </div>
              </div>
            </div>
          </div>

          <!-- 处理节点 -->
          <div class="palette-section">
            <div class="section-title">
              <i class="el-icon-cpu"></i>
              <span>处理</span>
            </div>
            <div class="node-items">
              <div 
                class="node-item" 
                draggable="true"
                @dragstart="onNodeDragStart"
                data-type="function"
              >
                <div class="node-icon function"></div>
                <div class="node-info">
                  <div class="node-name">函数</div>
                  <div class="node-desc">JavaScript函数</div>
            </div>
              </div>
              
              <div 
                class="node-item" 
                draggable="true"
                @dragstart="onNodeDragStart"
                data-type="switch"
              >
                <div class="node-icon switch"></div>
                <div class="node-info">
                  <div class="node-name">开关</div>
                  <div class="node-desc">条件判断</div>
            </div>
          </div>
          
              <div 
                class="node-item" 
                draggable="true"
                @dragstart="onNodeDragStart"
                data-type="delay"
              >
                <div class="node-icon delay"></div>
                <div class="node-info">
                  <div class="node-name">延迟</div>
                  <div class="node-desc">延时处理</div>
                </div>
              </div>
            </div>
          </div>

          <!-- 输出节点 -->
          <div class="palette-section">
            <div class="section-title">
              <i class="el-icon-s-promotion"></i>
              <span>输出</span>
            </div>
            <div class="node-items">
              <div 
                class="node-item" 
                draggable="true"
                @dragstart="onNodeDragStart"
                data-type="mqtt-out"
              >
                <div class="node-icon mqtt-out"></div>
                <div class="node-info">
                  <div class="node-name">MQTT输出</div>
                  <div class="node-desc">发送MQTT消息</div>
            </div>
            </div>
              
              <div 
                class="node-item" 
                draggable="true"
                @dragstart="onNodeDragStart"
                data-type="http-request"
              >
                <div class="node-icon http-request"></div>
                <div class="node-info">
                  <div class="node-name">HTTP请求</div>
                  <div class="node-desc">发送HTTP请求</div>
            </div>
            </div>
              
              <div 
                class="node-item" 
                draggable="true"
                @dragstart="onNodeDragStart"
                data-type="database"
              >
                <div class="node-icon database"></div>
                <div class="node-info">
                  <div class="node-name">数据库</div>
                  <div class="node-desc">数据库操作</div>
          </div>
        </div>
        
              <div 
                class="node-item" 
                draggable="true"
                @dragstart="onNodeDragStart"
                data-type="redis"
              >
                <div class="node-icon redis"></div>
                <div class="node-info">
                  <div class="node-name">Redis</div>
                  <div class="node-desc">Redis操作</div>
                </div>
              </div>
              
              <div 
                class="node-item" 
                draggable="true"
                @dragstart="onNodeDragStart"
                data-type="alarm"
              >
                <div class="node-icon alarm"></div>
                <div class="node-info">
                  <div class="node-name">告警</div>
                  <div class="node-desc">触发告警</div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>

      <!-- 主画布区域 -->
        <div class="canvas-container">
          <div 
          id="node-red-canvas"
          class="node-red-canvas"
          @drop="onCanvasDrop"
          @dragover="onCanvasDragOver"
            @click="onCanvasClick"
          ></div>
        </div>
        
        <!-- 属性面板 -->
        <div class="properties-panel" v-if="selectedNode">
        <div class="panel-header">
          <h4>节点属性</h4>
          <el-button circle plain type="danger" size="small" @click="deleteSelectedNode" :title="'删除当前节点'">
            <i class="el-icon-delete-solid"></i>
          </el-button>
        </div>
        
        <div class="panel-content">
          <el-form :model="nodeProperties" label-width="80px" size="small">
            <el-form-item label="节点名称">
              <el-input v-model="nodeProperties.name" @input="onNodePropertyChange" />
            </el-form-item>
            
            <el-form-item label="节点类型">
              <el-tag>{{ nodeProperties.type }}</el-tag>
            </el-form-item>
            
            <el-form-item label="描述">
              <el-input v-model="nodeProperties.description" type="textarea" @input="onNodePropertyChange" />
            </el-form-item>
            
            <!-- 设备事件配置 -->
            <template v-if="nodeProperties.type === 'device-event'">
              <el-divider>设备事件配置</el-divider>
              <el-form-item label="事件类型">
                <el-select v-model="nodeProperties.config.eventType" @change="onNodePropertyChange">
                  <el-option label="设备上线" value="online" />
                  <el-option label="设备下线" value="offline" />
                  <el-option label="数据上报" value="data" />
                </el-select>
              </el-form-item>
              <el-form-item label="设备编码">
                <el-input v-model="nodeProperties.config.deviceCode" placeholder="留空表示所有设备" @input="onNodePropertyChange" />
              </el-form-item>
            </template>
            
            <!-- MQTT输入配置 -->
            <template v-if="nodeProperties.type === 'mqtt-in'">
              <el-divider>MQTT配置</el-divider>
              <el-form-item label="主题">
                <el-input v-model="nodeProperties.config.topic" placeholder="例如: devices/{deviceCode}/trigger" @input="onNodePropertyChange" />
              </el-form-item>
              <el-form-item label="QoS">
                <el-select v-model="nodeProperties.config.qos" @change="onNodePropertyChange">
                  <el-option label="0 - 最多一次" :value="0" />
                  <el-option label="1 - 至少一次" :value="1" />
                  <el-option label="2 - 仅一次" :value="2" />
                </el-select>
              </el-form-item>
            </template>
            
            <!-- MQTT输出配置 -->
            <template v-if="nodeProperties.type === 'mqtt-out'">
              <el-divider>MQTT配置</el-divider>
              <el-form-item label="主题">
                <el-input v-model="nodeProperties.config.topic" placeholder="例如: devices/{deviceCode}/data" />
              </el-form-item>
              <el-form-item label="QoS">
                <el-select v-model="nodeProperties.config.qos">
                  <el-option label="0 - 最多一次" :value="0" />
                  <el-option label="1 - 至少一次" :value="1" />
                  <el-option label="2 - 仅一次" :value="2" />
                </el-select>
              </el-form-item>
              <el-form-item label="保留消息">
                <el-switch v-model="nodeProperties.config.retain" />
              </el-form-item>
            </template>
            
            <!-- HTTP请求配置 -->
            <template v-if="nodeProperties.type === 'http-request'">
              <el-divider>HTTP配置</el-divider>
              <el-form-item label="URL">
                <el-input v-model="nodeProperties.config.url" placeholder="例如: http://api.example.com/webhook" @input="onNodePropertyChange" />
              </el-form-item>
              <el-form-item label="方法">
                <el-select v-model="nodeProperties.config.method" @change="onNodePropertyChange">
                  <el-option label="GET" value="GET" />
                  <el-option label="POST" value="POST" />
                  <el-option label="PUT" value="PUT" />
                  <el-option label="DELETE" value="DELETE" />
                </el-select>
              </el-form-item>
              <el-form-item label="超时(ms)">
                <el-input-number v-model="nodeProperties.config.timeout" :min="1000" :max="30000" @change="onNodePropertyChange" />
              </el-form-item>
            </template>
            
            <!-- 数据库配置 -->
            <template v-if="nodeProperties.type === 'database'">
              <el-divider>数据库配置</el-divider>
              <el-form-item label="表名">
                <el-input v-model="nodeProperties.config.table" placeholder="例如: iot_data" />
              </el-form-item>
              <el-form-item label="操作">
                <el-select v-model="nodeProperties.config.operation">
                  <el-option label="插入" value="insert" />
                  <el-option label="更新" value="update" />
                  <el-option label="删除" value="delete" />
                </el-select>
              </el-form-item>
            </template>
            
            <!-- Redis配置 -->
            <template v-if="nodeProperties.type === 'redis'">
              <el-divider>Redis配置</el-divider>
              <el-form-item label="键名">
                <el-input v-model="nodeProperties.config.key" placeholder="例如: device:{deviceCode}:data" />
              </el-form-item>
              <el-form-item label="操作">
                <el-select v-model="nodeProperties.config.operation">
                  <el-option label="设置" value="set" />
                  <el-option label="获取" value="get" />
                  <el-option label="删除" value="del" />
                </el-select>
              </el-form-item>
              <el-form-item label="过期时间(s)" v-if="nodeProperties.config.operation === 'set'">
                <el-input-number v-model="nodeProperties.config.expire" :min="0" />
              </el-form-item>
            </template>
            
            <!-- 告警配置 -->
            <template v-if="nodeProperties.type === 'alarm'">
              <el-divider>告警配置</el-divider>
              <el-form-item label="告警类型">
                <el-select v-model="nodeProperties.config.alarmType">
                  <el-option label="阈值告警" value="threshold" />
                  <el-option label="状态告警" value="status" />
                  <el-option label="异常告警" value="exception" />
                </el-select>
              </el-form-item>
              <template v-if="nodeProperties.config.alarmType === 'threshold'">
                <el-form-item label="阈值">
                  <el-input-number v-model="nodeProperties.config.threshold" />
              </el-form-item>
                <el-form-item label="比较符">
                <el-select v-model="nodeProperties.config.operator">
                  <el-option label="大于" value=">" />
                  <el-option label="小于" value="<" />
                  <el-option label="等于" value="==" />
                  <el-option label="不等于" value="!=" />
                    <el-option label="大于等于" value=">=" />
                    <el-option label="小于等于" value="<=" />
                </el-select>
                </el-form-item>
              </template>
            </template>

            <!-- 函数配置 -->
            <template v-if="nodeProperties.type === 'function'">
              <el-divider>函数配置</el-divider>
              <el-form-item label="JavaScript代码">
                <el-input 
                  v-model="nodeProperties.config.code" 
                  type="textarea" 
                  :rows="8"
                  placeholder="// 处理消息的JavaScript代码&#10;return msg;"
                />
              </el-form-item>
            </template>
            
            <!-- 定时器配置 -->
            <template v-if="nodeProperties.type === 'timer'">
              <el-divider>定时器配置</el-divider>
              <el-form-item label="触发方式">
                <el-radio-group v-model="timerType">
                  <el-radio label="interval">固定间隔</el-radio>
                  <el-radio label="cron">Cron表达式</el-radio>
                </el-radio-group>
              </el-form-item>
              
              <el-form-item v-if="timerType === 'interval'" label="间隔(毫秒)">
                <el-input-number 
                  v-model="nodeProperties.config.interval" 
                  :min="1000" 
                  :max="3600000" 
                  :step="1000"
                  placeholder="5000" 
                />
              </el-form-item>
              
              <el-form-item v-if="timerType === 'cron'" label="Cron表达式">
                <el-input 
                  v-model="nodeProperties.config.cron" 
                  placeholder="0 */5 * * * *"
                />
                <div class="el-form-item__help">
                  例如：0 */5 * * * * (每5分钟执行一次)
                </div>
              </el-form-item>
              
              <el-form-item label="消息内容">
                <el-input 
                  v-model="nodeProperties.config.payload" 
                  type="textarea" 
                  placeholder='{"trigger": "timer"}'
                  :rows="3"
                />
              </el-form-item>
              
              <el-form-item label="启用定时器">
                <el-switch v-model="nodeProperties.config.enabled" />
              </el-form-item>
            </template>
            
            <el-form-item>
              <el-button type="primary" @click="updateNodeProperties" size="small">
                <i class="el-icon-check"></i> 更新
              </el-button>
            </el-form-item>
          </el-form>
        </div>
      </div>
    </div>
    <!-- 连线后创建节点的选择器 -->
    <el-dialog v-model="pickerVisible" title="选择要创建并连接的节点" width="520px">
      <el-tabs v-model="pickerTab">
        <el-tab-pane label="处理" name="process">
          <div class="picker-grid">
            <el-button v-for="item in pickerProcessNodes" :key="item.type" @click="createNodeFromPicker(item.type)" plain>
              {{ item.name }}
            </el-button>
          </div>
        </el-tab-pane>
        <el-tab-pane label="输出" name="output">
          <div class="picker-grid">
            <el-button v-for="item in pickerOutputNodes" :key="item.type" @click="createNodeFromPicker(item.type)" plain>
              {{ item.name }}
            </el-button>
          </div>
        </el-tab-pane>
      </el-tabs>
    </el-dialog>

    <!-- JSON 预览弹窗 -->
    <el-dialog v-model="previewVisible" title="当前规则 JSON 预览" width="720px">
      <el-input v-model="previewJson" type="textarea" :rows="20" readonly />
      <template #footer>
        <el-button @click="copyPreview">复制</el-button>
        <el-button type="primary" @click="previewVisible=false">关闭</el-button>
      </template>
    </el-dialog>

    <!-- 打开已有规则弹窗 -->
    <el-dialog v-model="openVisible" title="打开规则" width="720px">
      <div style="display:flex; gap:8px; align-items:center;">
        <el-input v-model="openQuery" placeholder="按名称搜索" clearable @keyup.enter="onSearchOpen" />
        <el-button @click="onSearchOpen">搜索</el-button>
      </div>
      <el-table :data="flowList" style="width:100%;margin-top:10px" height="360">
        <el-table-column prop="flowId" label="ID" width="80" />
        <el-table-column prop="flowName" label="名称" />
        <el-table-column prop="updateTime" label="更新时间" width="180" />
        <el-table-column label="操作" width="120">
          <template #default="scope">
            <el-button type="primary" link @click="loadFlow(scope.row.flowId)">打开</el-button>
          </template>
        </el-table-column>
      </el-table>
      <el-pagination
        style="margin-top: 10px; text-align:right;"
        v-model:current-page="openPageNum"
        v-model:page-size="openPageSize"
        :total="openTotal"
        @current-change="loadFlowList"
        @size-change="onOpenSizeChange"
      />
      <template #footer>
        <el-button @click="openVisible=false">关闭</el-button>
      </template>
    </el-dialog>

    <!-- 导入弹窗 -->
    <el-dialog v-model="importVisible" title="导入规则 JSON" width="720px">
      <el-input v-model="importJson" type="textarea" :rows="18" placeholder="粘贴或选择 JSON 文件" />
      <div style="margin-top:10px; display:flex; gap:8px; align-items:center;">
        <input ref="fileInput" type="file" accept="application/json" @change="onPickFile" />
        <el-button @click="pickFile">选择文件</el-button>
      </div>
      <template #footer>
        <el-button @click="importVisible=false">取消</el-button>
        <el-button type="primary" @click="doImport">导入</el-button>
      </template>
    </el-dialog>
  </div>
  </template>
  
  <script setup lang="ts">
import { ref, onMounted, computed, nextTick, watch, onActivated, onUnmounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
  import * as d3 from 'd3'
import { getNodeRedFlow, getNodeRedFlows, createNodeRedFlow, updateNodeRedFlow, deployFlow as apiDeployFlow, stopFlow as apiStopFlow } from '@/api/iot/nodered'
  
  // 响应式数据
const flowName = ref('新规则流程')
  const flowDescription = ref('')
  const currentFlowId = ref<number | null>(null)
  const selectedNode = ref(null)
  const nodeProperties = ref({
    id: '',
    name: '',
    type: '',
    description: '',
    config: {}
  })
  
  // 画布相关
let svgSel: any = null
  let canvas: any = null
  let nodes: any[] = []
  let links: any[] = []
  let nodeCounter = 0
let linkCounter = 0
let isConnecting = false
let connectionArmed = false
let connectionStart: any = null
let tempLine: any = null

// 计算属性（替换原来的 hasNodes）
const nodeCount = ref(0)
const hasNodes = computed(() => nodeCount.value > 0)

// 预览状态
const previewVisible = ref(false)
const previewJson = ref('')

// 打开已有规则
const openVisible = ref(false)
const openQuery = ref('')
const flowList = ref<any[]>([])
const openPageNum = ref(1)
const openPageSize = ref(10)
const openTotal = ref(0)

// 导入状态
const importVisible = ref(false)
const importJson = ref('')
const fileInput = ref<HTMLInputElement | null>(null)

// 选择器状态
const pickerVisible = ref(false)
const pickerTab = ref('process')
let pickerPosition = { x: 0, y: 0 }
const pickerProcessNodes = [
  { type: 'function', name: '函数' },
  { type: 'switch', name: '开关' },
  { type: 'delay', name: '延迟' },
  { type: 'http-request', name: 'HTTP请求' },
  { type: 'timer', name: '定时器' }
]
const pickerOutputNodes = [
  { type: 'mqtt-out', name: 'MQTT输出' },
  { type: 'database', name: '数据库' },
  { type: 'redis', name: 'Redis' },
  { type: 'alarm', name: '告警' }
]
  
  // 节点模板
  const nodeTemplates = {
    'device-event': {
      name: '设备事件',
      type: 'device-event',
      inputs: 0,
      outputs: 1,
    color: '#4CAF50',
      config: {
        eventType: 'online',
        deviceCode: ''
      }
    },
    'mqtt-in': {
      name: 'MQTT输入',
      type: 'mqtt-in',
      inputs: 0,
      outputs: 1,
    color: '#2196F3',
      config: {
        topic: '',
        qos: 0
      }
    },
  'http-in': {
    name: 'HTTP输入',
    type: 'http-in',
    inputs: 0,
    outputs: 1,
    color: '#FF9800',
    config: {
      url: '',
      method: 'POST'
    }
  },
  'function': {
    name: '函数',
    type: 'function',
    inputs: 1,
    outputs: 1,
    color: '#9C27B0',
    config: {
      code: 'return msg;'
    }
  },
  'switch': {
    name: '开关',
    type: 'switch',
    inputs: 1,
    outputs: 2,
    color: '#607D8B',
    config: {
      property: 'payload',
      rules: []
    }
  },
  'delay': {
    name: '延迟',
    type: 'delay',
    inputs: 1,
    outputs: 1,
    color: '#795548',
    config: {
      delay: 1000
    }
  },
    'mqtt-out': {
      name: 'MQTT输出',
      type: 'mqtt-out',
      inputs: 1,
      outputs: 0,
    color: '#2196F3',
      config: {
        topic: '',
      qos: 0,
      retain: false
      }
    },
    'http-request': {
      name: 'HTTP请求',
      type: 'http-request',
      inputs: 1,
      outputs: 1,
    color: '#FF9800',
      config: {
        url: '',
      method: 'POST',
      timeout: 5000
      }
    },
    'database': {
      name: '数据库',
      type: 'database',
      inputs: 1,
      outputs: 0,
    color: '#4CAF50',
      config: {
        table: '',
        operation: 'insert'
      }
    },
    'redis': {
      name: 'Redis',
      type: 'redis',
      inputs: 1,
      outputs: 0,
    color: '#F44336',
      config: {
        key: '',
      operation: 'set',
      expire: 0
      }
    },
    'alarm': {
      name: '告警',
      type: 'alarm',
      inputs: 1,
      outputs: 0,
    color: '#FF5722',
      config: {
        alarmType: 'threshold',
        threshold: 100,
        operator: '>'
      }
    },
    'timer': {
      name: '定时器',
      type: 'timer',
      inputs: 0,
      outputs: 1,
      color: '#FF5722',
      config: {
        interval: 5000,  // 默认5秒间隔
        cron: '',        // cron表达式（可选）
        payload: '{}',   // 定时触发的消息内容
        enabled: true    // 是否启用
      }
    }
  }
  
const route = useRoute()
const router = useRouter()

function closeEditor() {
  router.back()
}

async function waitForCanvasReady(maxWaitMs = 800) {
  const start = Date.now()
  while (Date.now() - start < maxWaitMs) {
    const el = document.getElementById('node-red-canvas')
    if (el && el.clientWidth > 0 && el.clientHeight > 0) return
    await new Promise(r => setTimeout(r, 50))
  }
}

async function loadFromRoute() {
  const flowId = route.query.flowId || route.params.flowId
  if (!flowId) return false  // 返回 false 表示没有数据
  
  try {
    // 确保画布已经初始化
    await nextTick()
    await waitForCanvasReady()
    
    // 如果画布还没有初始化，先初始化
    if (!svgSel || !canvas) {
      initCanvas()
      // 等待画布完全初始化
      await nextTick()
    }
    
    const resp = await getNodeRedFlow(String(flowId))
    const flow = resp?.data || resp
    if (!flow) return false
    
    // 保存当前正在编辑的 Flow ID
    currentFlowId.value = flow.flowId ?? null
    
    flowName.value = flow.flowName || '规则流程'
    flowDescription.value = flow.flowDescription || ''
    
    // 先清空现有数据
    nodes = []
    links = []
    nodeCount.value = 0
    
    // 使用与 loadFlow 相同的逻辑处理 flowConfig
    const jsonText = typeof flow.flowConfig === 'string'
      ? flow.flowConfig
      : JSON.stringify(flow.flowConfig || {})
    
    loadFlowFromJson(jsonText)
    
    // 确保渲染完成
    await nextTick()
    
    // 手动调用一次 renderNodes，确保渲染
    if (canvas && nodes.length > 0) {
      renderNodes()
    }
    
    ElMessage.success('已加载已保存的规则')
    return true  // 返回 true 表示成功加载了数据
  } catch (e) {
    ElMessage.error('加载规则失败')
    return false  // 返回 false 表示加载失败
  }
}

onMounted(async () => {
  await nextTick()
  await waitForCanvasReady()
  initCanvas()
  
  // 检查是否是编辑现有规则
  const flowId = route.query.flowId || route.params.flowId
  if (flowId) {
    // 编辑现有规则：先尝试从路由加载，失败时加载本地保存
    const hasRouteData = await loadFromRoute()
    if (!hasRouteData) {
      loadFromLocal()
    }
  } else {
    // 新建规则：清空画布，不加载任何数据
    clearCanvas()
  }
})

onActivated(async () => {
  await loadFromRoute()
})

watch(() => route.query.flowId, async () => {
  await loadFromRoute()
})
  
  function initCanvas() {
  const container = document.getElementById('node-red-canvas')
    if (!container) return
    
  // 创建 SVG 画布（svg 负责事件与尺寸，canvas 作为缩放/平移根容器）
  svgSel = d3.select(container)
      .append('svg')
      .attr('width', '100%')
      .attr('height', '100%')
    .style('background', 'transparent')

  canvas = svgSel.append('g').attr('class', 'root')
    
    // 添加缩放和平移功能
    const zoom = d3.zoom()
      .scaleExtent([0.1, 3])
      .on('zoom', (event) => {
        canvas.attr('transform', event.transform)
      })
    
  svgSel.call(zoom)
  
  // 添加连线层和节点层
  canvas.append('g').attr('class', 'links')
  canvas.append('g').attr('class', 'nodes')
  }
  
function onNodeDragStart(event: DragEvent) {
    const nodeType = (event.target as HTMLElement).getAttribute('data-type')
    if (event.dataTransfer) {
      event.dataTransfer.setData('text/plain', nodeType || '')
    }
  }
  
function onCanvasDragOver(event: DragEvent) {
    event.preventDefault()
  }
  
function onCanvasDrop(event: DragEvent) {
    event.preventDefault()
    const nodeType = event.dataTransfer?.getData('text/plain')
    if (!nodeType) return
    
    const rect = (event.target as HTMLElement).getBoundingClientRect()
    const x = event.clientX - rect.left
    const y = event.clientY - rect.top
    
    createNode(nodeType, x, y)
  }
  
  function createNode(type: string, x: number, y: number) {
    const template = nodeTemplates[type]
    if (!template) return
    
    const node = {
      id: `node_${++nodeCounter}`,
      name: template.name,
      type: template.type,
      x: x,
      y: y,
      inputs: template.inputs,
      outputs: template.outputs,
    color: template.color,
      config: { ...template.config }
    }
    
    nodes.push(node)
  nodeCount.value = nodes.length
    renderNodes()
  ElMessage.success(`已添加${template.name}节点`)
  }
  
  function renderNodes() {
    if (!canvas) return
    
    // 清理连接状态，防止虚线残留
    if (isConnecting) {
      cleanupConnection()
    }
    
    // 清除现有节点
  canvas.select('.nodes').selectAll('.node').remove()
    
    // 渲染节点
  const nodeGroups = canvas.select('.nodes')
    .selectAll('.node')
      .data(nodes)
      .enter()
      .append('g')
      .attr('class', 'node')
      .attr('transform', d => `translate(${d.x}, ${d.y})`)
      .style('cursor', 'move')
    
    // 节点背景
    nodeGroups.append('rect')
    .attr('width', 140)
    .attr('height', 80)
    .attr('rx', 8)
      .attr('fill', '#fff')
    .attr('stroke', d => d.color)
      .attr('stroke-width', 2)
    .attr('filter', 'drop-shadow(0 2px 4px rgba(0,0,0,0.1))')
  
  // 右上角删除按钮（更明显）
  nodeGroups.append('circle')
    .attr('class', 'node-delete')
    .attr('cx', 132)
    .attr('cy', 8)
    .attr('r', 10)
    .attr('fill', '#ff4d4f')
    .attr('stroke', '#fff')
    .attr('stroke-width', 2)
    .style('cursor', 'pointer')
    .on('click', function(event, d) {
      event.stopPropagation();
      ElMessageBox.confirm('确认删除该节点吗？', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        deleteNodeById(d.id);
      }).catch(() => {});
    })
    nodeGroups.append('text')
    .attr('x', 132)
    .attr('y', 12)
      .attr('text-anchor', 'middle')
      .attr('font-size', '12px')
    .attr('fill', '#fff')
    .attr('pointer-events', 'none')
    .text('×')
  
  // 节点标题
  nodeGroups.append('text')
    .attr('x', 70)
    .attr('y', 25)
    .attr('text-anchor', 'middle')
    .attr('font-size', '14px')
      .attr('font-weight', 'bold')
    .attr('fill', '#333')
      .text(d => d.name)
    
    // 节点类型
    nodeGroups.append('text')
    .attr('x', 70)
    .attr('y', 45)
      .attr('text-anchor', 'middle')
    .attr('font-size', '11px')
      .attr('fill', '#666')
      .text(d => d.type)
    
    // 输入端口
    nodeGroups.selectAll('.input-port')
    .data(d => Array(d.inputs).fill(0).map((_, i) => ({ node: d, index: i, total: d.inputs })))
      .enter()
      .append('circle')
      .attr('class', 'input-port')
      .attr('cx', 0)
    .attr('cy', d => 40 + (d.index - (d.total - 1) / 2) * 20)
    .attr('r', 6)
      .attr('fill', '#4CAF50')
    .attr('stroke', '#fff')
    .attr('stroke-width', 2)
      .style('cursor', 'pointer')
    .on('mouseup', function(event, d) {
      event.preventDefault()
      event.stopPropagation()
      endConnection(d.node, 'input')
    })
    
    // 输出端口
    nodeGroups.selectAll('.output-port')
    .data(d => Array(d.outputs).fill(0).map((_, i) => ({ node: d, index: i, total: d.outputs })))
      .enter()
      .append('circle')
      .attr('class', 'output-port')
    .attr('cx', 140)
    .attr('cy', d => 40 + (d.index - (d.total - 1) / 2) * 20)
    .attr('r', 6)
      .attr('fill', '#2196F3')
    .attr('stroke', '#fff')
    .attr('stroke-width', 2)
      .style('cursor', 'pointer')
    .on('mousedown', function(event, d) {
      event.preventDefault()
      event.stopPropagation()
      startConnection(d.node, 'output', event, d.index)
    })
    .on('contextmenu', function(event, d) {
      event.preventDefault()
      event.stopPropagation()
      // 右键点击输出端口，弹出节点选择器
      const sourceX = d.node.x + 140
      const sourceY = d.node.y + 40 + (d.index - (d.total - 1) / 2) * 20
      pickerPosition = { x: sourceX + 40, y: sourceY }
      // 设置连接起始点，用于自动连接新创建的节点
      connectionStart = { node: d.node, type: 'output', outIndex: d.index }
      pickerVisible.value = true
    })
    
    // 添加拖拽功能
    const drag = d3.drag()
      .on('start', function(event, d) {
        // 如果正在连接，取消拖拽
        if (isConnecting) {
          return false
        }
        // 防止文字选择
        event.sourceEvent.preventDefault()
        d3.select(this).raise()
      })
      .on('drag', function(event, d) {
        // 如果正在连接，取消拖拽
        if (isConnecting) {
          return false
        }
        // 防止文字选择
        event.sourceEvent.preventDefault()
        d.x = event.x
        d.y = event.y
        d3.select(this).attr('transform', `translate(${d.x}, ${d.y})`)
        updateLinks()
      })
    
    nodeGroups.call(drag)
    
    // 添加点击事件
    nodeGroups.on('click', function(event, d) {
      event.stopPropagation()
      selectNode(d)
    })
  
  // 渲染连线
  renderLinks()
}

function startConnection(node: any, type: string, event: any, outIndex?: number) {
  if (type === 'output') {
    // 如果已经在连接状态，先清理
    if (isConnecting) {
      cleanupConnection()
    }
    
    isConnecting = true
    connectionStart = { node, type, outIndex }
    
    // 添加临时连线预览
    tempLine = canvas.select('.links')
      .append('path')
      .attr('class', 'temp-link')
      .attr('stroke', '#ff6b6b')
      .attr('stroke-width', 3)
      .attr('fill', 'none')
      .attr('stroke-dasharray', '5,5')
    
    const mouseMove = (ev: any) => {
      const [x, y] = d3.pointer(ev, svgSel.node())
      const sourceX = node.x + 140
      const sourceY = node.y + 40 + ((outIndex ?? 0) - ((node.outputs ?? 1) - 1) / 2) * 20
      
      // 检查鼠标是否悬停在输入端口上
      let targetX = x
      let targetY = y
      let isSnapped = false
      
      // 查找最近的输入端口
      const targetNode = findNearestInputPort(x, y)
      if (targetNode) {
        targetX = targetNode.x
        targetY = targetNode.y + 40 // 输入端口在节点左侧中央
        isSnapped = true
      }
      
      // 创建贝塞尔曲线路径，类似已连接的线
      const midX = (sourceX + targetX) / 2
      const midY = Math.min(sourceY, targetY) - 20
      
      const path = `M ${sourceX} ${sourceY} Q ${midX} ${midY} ${targetX} ${targetY}`
      tempLine.attr('d', path)
      
      // 如果吸附到输入端口，改变线条样式
      if (isSnapped) {
        tempLine.attr('stroke', '#4CAF50').attr('stroke-width', 4)
      } else {
        tempLine.attr('stroke', '#ff6b6b').attr('stroke-width', 3)
      }
      
      pickerPosition = { x, y }
    }
    
    const mouseUp = (event: any) => {
      svgSel.on('mousemove.temp', null)
      svgSel.on('mouseup.temp', null)
      // 处理鼠标释放事件
      handleMouseUp(event)
    }
    
    svgSel.on('mousemove.temp', mouseMove)
    svgSel.on('mouseup.temp', mouseUp)
  }
}

function endConnection(node: any, type: string) {
  if (isConnecting && connectionStart && type === 'input') {
    // 创建连线
    const link = {
      id: `link_${++linkCounter}`,
      source: connectionStart.node.id,
      target: node.id
    }
    
    // 检查是否已存在相同连线
    const exists = links.some((l: any) => l.source === link.source && l.target === link.target)
    if (!exists) {
      links.push(link)
      renderLinks()
      ElMessage.success('连线创建成功')
    } else {
      ElMessage.warning('连线已存在')
    }
    
    cleanupConnection()
  }
}

// 处理鼠标释放事件，检查是否在输入端口上释放
function handleMouseUp(event: any) {
  if (isConnecting && connectionStart) {
    const [x, y] = d3.pointer(event, svgSel.node())
    const targetNode = findNearestInputPort(x, y)
    
    // 检查连接起始节点是否仍然存在
    const sourceNodeExists = nodes.some(n => n.id === connectionStart.node.id)
    if (!sourceNodeExists) {
      // 起始节点已被删除，取消连接
      cleanupConnection()
      return
    }
    
    if (targetNode) {
      // 在输入端口上释放，创建连接
      endConnection(targetNode, 'input')
    } else {
      // 不在输入端口上，取消连接
      cleanupConnection()
    }
  }
}

// 清理连接状态
function cleanupConnection() {
  isConnecting = false
  connectionStart = null
  if (tempLine) {
    tempLine.remove()
    tempLine = null
  }
  svgSel.on('mousemove.temp', null)
  svgSel.on('mouseup.temp', null)
}

// 查找最近的输入端口
function findNearestInputPort(x: number, y: number) {
  const threshold = 30 // 吸附距离阈值
  let nearestNode = null
  let minDistance = Infinity
  
  // 确保nodes数组是最新的
  if (!nodes || nodes.length === 0) {
    return null
  }
  
  for (const node of nodes) {
    if (node && node.inputs > 0) {
      const inputX = node.x
      const inputY = node.y + 40 // 输入端口在节点左侧中央
      const distance = Math.sqrt((x - inputX) ** 2 + (y - inputY) ** 2)
      
      if (distance < threshold && distance < minDistance) {
        minDistance = distance
        nearestNode = node
      }
    }
  }
  
  return nearestNode
}

function renderLinks() {
  if (!canvas) return
  
  const linkGroup = canvas.select('.links')
  linkGroup.selectAll('.link').remove()
  
  linkGroup.selectAll('.link')
    .data(links)
    .enter()
    .append('path')
    .attr('class', 'link')
    .attr('d', d => {
      const sourceNode = nodes.find((n: any) => n.id === d.source)
      const targetNode = nodes.find((n: any) => n.id === d.target)
      if (!sourceNode || !targetNode) return ''
      
      const sourceX = sourceNode.x + 140
      const sourceY = sourceNode.y + 40 // 与端口居中一致
      const targetX = targetNode.x
      const targetY = targetNode.y + 40 // 与端口居中一致
      
      const cx = (sourceX + targetX) / 2
      const cy = sourceY - 20
      
      return `M ${sourceX} ${sourceY} Q ${cx} ${cy} ${targetX} ${targetY}`
    })
    .attr('fill', 'none')
    .attr('stroke', '#666')
    .attr('stroke-width', 2)
    .style('cursor', 'pointer')
    .on('click', function(event, d) {
      event.stopPropagation()
      deleteLink(d.id)
    })
}

function updateLinks() {
  renderLinks()
}

function deleteLink(linkId: string) {
  ElMessageBox.confirm('确认删除这条连线吗？', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    const index = links.findIndex(l => l.id === linkId)
    if (index > -1) {
      links.splice(index, 1)
      renderLinks()
      ElMessage.success('连线已删除')
    }
  })
  }
  
  function selectNode(node: any) {
    selectedNode.value = node
    nodeProperties.value = {
      id: node.id,
      name: node.name,
      type: node.type,
      description: node.description || '',
      config: { ...node.config }
    }
  }
  
  function onCanvasClick() {
    selectedNode.value = null
    // 如果正在连接，取消连接
    if (isConnecting) {
      cleanupConnection()
    }
  }
  
  // 添加定时器类型响应式变量
  const timerType = ref('interval')
  // 添加定时器管理
  const activeTimers = new Map()

  // 启动定时器
  function startTimer(nodeId: string, config: any) {
    if (!config.enabled) return
    stopTimer(nodeId)
    
    let timerId
    if (config.cron && config.cron.trim()) {
      // 使用 cron 表达式（这里简化实现，实际应该使用 cron 库）
      timerId = setInterval(() => {
        triggerTimer(nodeId, config)
      }, 5000) // 简化：每5秒检查一次
    } else {
      // 使用固定间隔
      timerId = setInterval(() => {
        triggerTimer(nodeId, config)
      }, config.interval || 5000)
    }
    
    activeTimers.set(nodeId, timerId)
    console.log(`Timer started for node ${nodeId}`)
  }

  // 停止定时器
  function stopTimer(nodeId: string) {
    const timerId = activeTimers.get(nodeId)
    if (timerId) {
      clearInterval(timerId)
      activeTimers.delete(nodeId)
      console.log(`Timer stopped for node ${nodeId}`)
    }
  }

  // 触发定时器
  function triggerTimer(nodeId: string, config: any) {
    try {
      const payload = JSON.parse(config.payload || '{}')
      const message = {
        id: `timer_${Date.now()}`,
        type: 'timer-trigger',
        payload: payload,
        metadata: {
          nodeId: nodeId,
          timestamp: Date.now()
        }
      }
      
      // 找到对应的节点并触发
      const node = nodes.find(n => n.id === nodeId)
      if (node) {
        // 这里应该触发规则执行
        console.log(`Timer triggered for node ${nodeId}:`, message)
        // TODO: 触发规则执行逻辑
      }
    } catch (e) {
      console.error(`Timer trigger error for node ${nodeId}:`, e)
    }
  }

  // 在节点属性更新时启动/停止定时器
  function updateNodeProperties() {
    if (!selectedNode.value) return
    
    const node = nodes.find(n => n.id === selectedNode.value.id)
    if (node) {
      node.name = nodeProperties.value.name
      node.description = nodeProperties.value.description
      node.config = { ...nodeProperties.value.config }
      renderNodes()
      
      // 如果是定时器节点，启动定时器
      if (node.type === 'timer') {
        startTimer(node.id, node.config)
      }
      
      // 自动保存到本地存储
      autoSaveToLocal()
    }
  }
  
  // 自动保存到本地存储
  function autoSaveToLocal() {
    try {
      const autoSaveData = {
        flowId: currentFlowId.value, // 保存当前规则ID
        flowName: flowName.value,
        flowDescription: flowDescription.value,
        nodes: nodes,
        links: links,
        timestamp: Date.now()
      }
      localStorage.setItem('rule-editor-auto-save', JSON.stringify(autoSaveData))
    } catch (e) {
      console.warn('自动保存失败:', e)
    }
  }
  
  // 从本地存储恢复
  function loadFromLocal() {
    try {
      const saved = localStorage.getItem('rule-editor-auto-save')
      if (saved) {
        const data = JSON.parse(saved)
        // 检查是否是今天的数据且与当前规则匹配
        const currentFlowId = route.query.flowId || route.params.flowId
        if (Date.now() - data.timestamp < 24 * 60 * 60 * 1000 && 
            data.flowId && data.flowId == currentFlowId) {
          flowName.value = data.flowName || '新规则流程'
          flowDescription.value = data.flowDescription || ''
          nodes = data.nodes || []
          links = data.links || []
          nodeCount.value = nodes.length
          renderNodes()
          return true
        }
      }
    } catch (e) {
      console.warn('加载本地保存失败:', e)
    }
    return false
  }

function deleteNodeById(nodeId: string) {
  // 如果正在连接且连接起始点是当前节点，清理连接状态
  if (isConnecting && connectionStart && connectionStart.node.id === nodeId) {
    cleanupConnection()
  }
  
  const index = nodes.findIndex((n: any) => n.id === nodeId)
    if (index > -1) {
    // 停止定时器
    stopTimer(nodeId)
    
    links = links.filter((l: any) => l.source !== nodeId && l.target !== nodeId)
      nodes.splice(index, 1)
    nodeCount.value = nodes.length
      renderNodes()
    if (selectedNode.value && selectedNode.value.id === nodeId) {
      selectedNode.value = null
    }
      ElMessage.success('节点已删除')
    }
  }
  
function deleteSelectedNode() {
  if (!selectedNode.value) return
  
  ElMessageBox.confirm('确认删除这个节点吗？', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    deleteNodeById(selectedNode.value!.id)
  })
}

function clearFlow() {
  ElMessageBox.confirm('确认清空所有节点和连线吗？', '提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning'
  }).then(() => {
    nodes = []
    links = []
    nodeCount.value = 0
    renderNodes()
    selectedNode.value = null
    ElMessage.success('画布已清空')
  })
  }
  
  async function saveFlow() {
    if (!flowName.value) {
      ElMessage.error('请输入Flow名称')
      return
    }
    
  const flowConfig = buildCurrentFlow()
    
    try {
      const flowData: any = {
        flowName: flowName.value,
        flowDescription: flowDescription.value,
        flowConfig: JSON.stringify(flowConfig),
        isActive: false,
        isRunning: false,
        version: 1
      }
      
      if (currentFlowId.value) {
        flowData.flowId = currentFlowId.value
        await updateNodeRedFlow(flowData)
        ElMessage.success('规则更新成功')
      } else {
        await createNodeRedFlow(flowData)
        ElMessage.success('规则创建成功')
      }
      // 保存成功后关闭页面
      router.back()
    } catch (error) {
      ElMessage.error('保存失败')
    }
  }
  
  async function deployFlow() {
    if (nodes.length === 0) {
      ElMessage.error('请先添加节点')
      return
    }
    
    try {
      // 这里应该调用部署API
      ElMessage.success('Flow部署成功')
    } catch (error) {
      ElMessage.error('Flow部署失败')
    }
  }
  
  async function stopFlow() {
    try {
      // 这里应该调用停止API
      ElMessage.success('Flow已停止')
    } catch (error) {
      ElMessage.error('停止Flow失败')
    }
  }
  
function openImport() {
  importVisible.value = true
}

function pickFile() {
  fileInput.value?.click()
}

function onPickFile(e: Event) {
  const input = e.target as HTMLInputElement
  const f = input.files?.[0]
  if (!f) return
  const reader = new FileReader()
  reader.onload = () => {
    importJson.value = String(reader.result || '')
  }
  reader.readAsText(f)
}

function doImport() {
  try {
    loadFlowFromJson(importJson.value)
    importVisible.value = false
    ElMessage.success('导入成功')
  } catch {
    ElMessage.error('JSON 解析失败')
  }
  }
  
  function exportFlow() {
  const flowConfig = buildCurrentFlow()
    const blob = new Blob([JSON.stringify(flowConfig, null, 2)], { type: 'application/json' })
    const url = URL.createObjectURL(blob)
    const a = document.createElement('a')
    a.href = url
    a.download = `${flowName.value || 'flow'}.json`
    a.click()
    URL.revokeObjectURL(url)
  ElMessage.success('Flow导出成功')
}

function buildCurrentFlow() {
  return {
    name: flowName.value,
    description: flowDescription.value,
    nodes: nodes,
    links: links
  }
}

function loadFlowFromJson(jsonText: string) {
  const parseSafe = (s: any) => {
    if (s == null) return {}
    if (typeof s === 'string') {
      const t = s.trim()
      if (!t) return {}
      try { return JSON.parse(t) } catch { return {} }
    }
    return s
  }
  let obj: any = parseSafe(jsonText)
  if (obj.flowConfig) obj = parseSafe(obj.flowConfig)
  if (!obj.nodes && typeof obj === 'string') obj = parseSafe(obj)

  // 兼容 Node-RED 导出格式：数组(含 tab 与节点)，或对象包含 flows 字段
  const tryConvertFromNodeRed = (input: any) => {
    const toInternalType = (t: string) => {
      if (!t) return 'function'
      const m: Record<string, string> = {
        'http request': 'http-request',
        'mqtt out': 'mqtt-out',
        'function': 'function',
        'switch': 'switch',
        'delay': 'delay'
      }
      // Node-RED 英文类型到内部类型的简单映射，未知则原样
      return m[t] || t
    }
    const isArray = Array.isArray(input)
    const flows = isArray ? input : (Array.isArray(input?.flows) ? input.flows : null)
    if (!Array.isArray(flows)) return null

    const redNodes = flows.filter((n: any) => n && n.type && n.type !== 'tab')
    const idMap: Record<string, string> = {}
    let tmpNodeCounter = nodeCounter
    const outNodes = redNodes.map((n: any) => {
      const newId = `node_${++tmpNodeCounter}`
      idMap[n.id] = newId
      return {
        id: newId,
        name: n.name || n.type || '节点',
        type: toInternalType(n.type),
        x: typeof n.x === 'number' ? n.x : 100,
        y: typeof n.y === 'number' ? n.y : 100,
        inputs: 1,
        outputs: Array.isArray(n.wires) ? Math.max(1, n.wires.length) : 1,
        color: '#2196F3',
        config: {}
      }
    })
    const outLinks: any[] = []
    redNodes.forEach((n: any) => {
      const src = idMap[n.id]
      const wires: any[][] = Array.isArray(n.wires) ? n.wires : []
      wires.forEach(arr => {
        (arr || []).forEach(tid => {
          const dst = idMap[tid]
          if (src && dst) outLinks.push({ id: `link_${++linkCounter}`, source: src, target: dst })
        })
      })
    })
    return { nodes: outNodes, links: outLinks }
  }

  // 若检测为 Node-RED，则转换
  if (!obj.nodes) {
    const converted = tryConvertFromNodeRed(obj)
    if (converted) {
      obj = converted
      // 设默认名称
      if (!flowName.value || flowName.value === '新规则流程') flowName.value = '导入的Node-RED流程'
    }
  }

  if (obj.name) flowName.value = obj.name
  if (obj.description != null) flowDescription.value = obj.description
  const nextNodes = Array.isArray(obj.nodes) ? obj.nodes : []
  const nextLinks = Array.isArray(obj.links) ? obj.links : []
  nodes = nextNodes
  links = nextLinks
  nodeCount.value = nodes.length
  if (!svgSel || !canvas) initCanvas()
  renderNodes()
}

function openPreview() {
  const data = buildCurrentFlow()
  previewJson.value = JSON.stringify(data, null, 2)
  previewVisible.value = true
}

async function copyPreview() {
  try {
    await navigator.clipboard.writeText(previewJson.value)
    ElMessage.success('已复制到剪贴板')
  } catch (e) {
    ElMessage.error('复制失败')
  }
}

function createNodeFromPicker(type: string) {
  if (!connectionStart) { 
    pickerVisible.value = false
    return 
  }
  
  const template = (nodeTemplates as any)[type]
  if (!template) { 
    pickerVisible.value = false
    return 
  }
  
  const node = {
    id: `node_${++nodeCounter}`,
    name: template.name,
    type: template.type,
    x: pickerPosition.x,
    y: pickerPosition.y,
    inputs: template.inputs,
    outputs: template.outputs,
    color: template.color,
    config: { ...template.config }
  }
  
  nodes.push(node)
  
  // 自动连线：connectionStart.node -> 新节点
  const link = { 
    id: `link_${++linkCounter}`, 
    source: connectionStart.node.id, 
    target: node.id 
  }
  const exists = links.some((l: any) => l.source === link.source && l.target === link.target)
  if (!exists) {
    links.push(link)
  }
  
  renderNodes()
  renderLinks()
  pickerVisible.value = false
  connectionStart = null
  nodeCount.value = nodes.length
  ElMessage.success(`已创建${template.name}节点并自动连接`)
}

// 打开规则弹窗逻辑
function openFlowDialog() {
  openVisible.value = true
  loadFlowList()
}

async function loadFlowList() {
  try {
    const { data } = await getNodeRedFlows({ pageNum: openPageNum.value, pageSize: openPageSize.value, flowName: openQuery.value })
    flowList.value = data?.records || []
    openTotal.value = data?.total || 0
  } catch (e) {
    // ignore
  }
}

function onSearchOpen() {
  openPageNum.value = 1
  loadFlowList()
}

function onOpenSizeChange() {
  openPageNum.value = 1
  loadFlowList()
}

async function loadFlow(id: number) {
  try {
    const resp = await getNodeRedFlow(id)
    const flow = resp?.data || resp // 兼容 R 包裹与直出
    if (!flow) return
    flowName.value = flow.flowName || '规则流程'
    flowDescription.value = flow.flowDescription || ''
    // 将 DB 的 flowConfig 当作"文本"喂给通用解析器
    const jsonText = typeof flow.flowConfig === 'string'
      ? flow.flowConfig
      : JSON.stringify(flow.flowConfig || {})
    loadFlowFromJson(jsonText)   // 内部已更新 nodeCount + renderNodes
    openVisible.value = false
    ElMessage.success('已打开规则')
  } catch {
    ElMessage.error('打开失败')
  }
}

function autoSave() {
  // 延迟保存，避免频繁保存
  if (autoSaveTimer) {
    clearTimeout(autoSaveTimer)
  }
  autoSaveTimer = setTimeout(() => {
    // 这里可以添加本地存储或自动保存到后端的逻辑
    localStorage.setItem('rule-editor-auto-save', JSON.stringify({
      flowName: flowName.value,
      flowDescription: flowDescription.value,
      nodes: nodes,
      links: links
    }))
  }, 1000)
}

// 节点属性变化时的处理函数
function onNodePropertyChange() {
  if (!selectedNode.value) return
  
  // 延迟保存，避免频繁保存
  if (autoSaveTimer) {
    clearTimeout(autoSaveTimer)
  }
  autoSaveTimer = setTimeout(() => {
    updateNodeProperties()
  }, 500)
}

// 添加定时器变量
let autoSaveTimer: any = null

// 添加清空画布函数
function clearCanvas() {
  nodes = []
  links = []
  nodeCount.value = 0
  selectedNode.value = null
  renderNodes()
  console.log('Canvas cleared for new rule')
}
  </script>
  
  <style scoped>
  .rule-editor-container {
    height: 100vh;
    display: flex;
    flex-direction: column;
  background: #f8f9fa;
  }
  
.editor-toolbar {
  padding: 12px 16px;
  background: #fff;
  border-bottom: 1px solid #e0e0e0;
    display: flex;
    justify-content: space-between;
    align-items: center;
  box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

.toolbar-left {
  display: flex;
  align-items: center;
  gap: 8px;
}

.toolbar-right {
  display: flex;
  align-items: center;
  }
  
  .editor-content {
    flex: 1;
    display: flex;
  height: calc(100vh - 80px);
  }
  
  .node-palette {
  width: 280px;
  background: #fff;
  border-right: 1px solid #e0e0e0;
    overflow-y: auto;
  }

.palette-header {
  padding: 16px;
  border-bottom: 1px solid #e0e0e0;
  background: #f5f5f5;
}

.palette-header h3 {
  margin: 0;
  font-size: 16px;
  color: #333;
}

.palette-sections {
  padding: 16px;
}
  
  .palette-section {
  margin-bottom: 24px;
  }
  
.section-title {
  display: flex;
  align-items: center;
  margin-bottom: 12px;
  font-weight: 600;
  color: #333;
}

.section-title i {
  margin-right: 8px;
    color: #666;
  }

.node-items {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
  
  .node-item {
    display: flex;
    align-items: center;
  padding: 12px;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
    cursor: grab;
    background: #fff;
    transition: all 0.2s;
  }
  
  .node-item:hover {
    border-color: #409EFF;
  box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
  transform: translateY(-1px);
  }
  
.node-icon {
  width: 32px;
  height: 32px;
  border-radius: 6px;
  margin-right: 12px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #fff;
  font-weight: bold;
  font-size: 12px;
}

.node-icon.device-event { background: #4CAF50; }
.node-icon.mqtt-in { background: #2196F3; }
.node-icon.http-in { background: #FF9800; }
.node-icon.function { background: #9C27B0; }
.node-icon.switch { background: #607D8B; }
.node-icon.delay { background: #795548; }
.node-icon.mqtt-out { background: #2196F3; }
.node-icon.http-request { background: #FF9800; }
.node-icon.database { background: #4CAF50; }
.node-icon.redis { background: #F44336; }
.node-icon.alarm { background: #FF5722; }
.node-icon.timer {
  background: #FF5722;
  border-radius: 50%;
  width: 24px;
  height: 24px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-size: 12px;
  font-weight: bold;
}

.node-icon.timer::before {
  content: "⏰";
}

.node-info {
  flex: 1;
}

.node-name {
  font-weight: 600;
  color: #333;
  margin-bottom: 2px;
}

.node-desc {
  font-size: 12px;
  color: #666;
  }
  
  .canvas-container {
    flex: 1;
    position: relative;
    overflow: hidden;
  }
  
.node-red-canvas {
    width: 100%;
    height: 100%;
  /* Node-RED 风格网格背景：浅细格 + 稍深粗格 */
  background-color: #fafafa;
  background-image:
    linear-gradient(#eaeaea 1px, transparent 1px),
    linear-gradient(90deg, #eaeaea 1px, transparent 1px),
    linear-gradient(#f0f0f0 1px, transparent 1px),
    linear-gradient(90deg, #f0f0f0 1px, transparent 1px);
  background-size: 20px 20px, 20px 20px, 100px 100px, 100px 100px;
  background-position: 0 0, 0 0, 0 0, 0 0;
  }
  
  .properties-panel {
  width: 320px;
  background: #fff;
  border-left: 1px solid #e0e0e0;
    overflow-y: auto;
  }
  
.panel-header {
  padding: 16px;
  border-bottom: 1px solid #e0e0e0;
  display: flex;
  justify-content: space-between;
  align-items: center;
  background: #f5f5f5;
}

.panel-header h4 {
  margin: 0;
    color: #333;
  }

.panel-content {
  padding: 16px;
}
  
  .node {
    cursor: move;
    user-select: none;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
  }
  
  .node:hover rect {
    stroke-width: 3;
  }
  
  /* 防止文字选择 */
  .node text {
    user-select: none;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    pointer-events: none;
  }
  
  /* 连接端口样式 */
  .input-port, .output-port {
    user-select: none;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
  }
  
  /* 临时连接线样式 */
  .temp-link {
    user-select: none;
    -webkit-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
  }
  
  .input-port:hover,
  .output-port:hover {
    r: 8;
  }

.link {
  cursor: pointer;
}

.link:hover {
  stroke: #ff6b6b;
  stroke-width: 3;
}

.temp-link {
  pointer-events: none;
  }
  </style>